\chapter{扩展实现}
\section{数据旁路}
为了在解决结构冲突、 数据冲突以及控制冲突的同时尽量提高系统效率， 需要向其中加入数据旁路， 使得运算结果能够尽快传递到需要使用它的地方。 在设计时我们考虑尽量简化系统结构， 添加从EXE段结果和MEM段结果引回至ID段与从寄存器堆中读取的结果进行选通。 为了判断何时使用数据通路， 在流水的过程中还同时传送操作的寄存器1,2以及写回寄存器的地址， 对于寄存器堆的输出， 只要发现EXE段结果或MEM段结果地址与之相同则使用数据旁路引回的值(优先选择EXE段的， 因为其数据更新)。 在此基础上， 对于结构冲突的解决方式是当存在内存访问时紧跟其后插入一个气泡， 这样不仅能避免结构冲突， 而且同时可以使得访存结果可以及时送到其后一条指令处。 而控制冲突在使用延时槽的基础上利用同样的数据通路即可避免。\\

总结下来， 整体有四条数据旁路， 仅在访存时插入一个气泡， 同时结合延时槽即可解决全部三种冲突。\\

以上是设计过程， 但是实现过程发现了其中的一个问题， 当出现结构冲突插入气泡时为了避免读取内存结果会被使用产生的数据冲突， 选择插入气泡的地方是紧跟在当前这条访存语句之后的， 但这样在取指部分就会产生问题（因为正在访存无法同时取指）， 一个简单的做法是多插入一个气泡， 但是为了效率我们并没有这样做， 而是观察发现此时无法取指但是在上一周期其指令实际已经取出， 因此只要再引入一个寄存器存储之前一个周期取指结果即可避免这样的冲突。\\

\section{中断}
所有中断都由IF阶段的中断处理部件完成，我们实现了软件中断、硬件中断以及外部中断这三种不同的中断。\\

经过分析， 我们发现中断实现过程实际上有两次访存， 无论如何是无法在一个周期完成的。 因此我们考虑将一条中断解释为以下四条语句：

\begin{enumerate}
\item INT1 : 将PC压栈
\item INT2 : 将中断号压栈
\item INT3 : 令IH高位置0并跳转至该位置
\item NOP  : 什么都不做， 作为跳转语句的延时槽
\end{enumerate}

并且将前三条原本指令集中没有的指令设计了16位指令格式(具体指令格式可以参照附件中控制信号表格)。 实际过程中INT1压栈同时不改变栈顶指针， 而INT2压栈后直接令SP-2。 而原本的INT指令只有四位无符号扩展立即数， 而INT2指令设计时为其提供了8位无符号扩展的立即数， 这样一来可以同样处理外部中断以及时钟中断。 \\

而这三条指令的加入需要对之前的数据通路进行一定的改动。 之前所有的写内存操作都是写入一个普通寄存器的值(即该寄存器位于寄存器堆)， 但是INT1和INT2实际上需要写入PC值(不在寄存器堆中，而是随流水传递)和一个立即数值， 因此需要对写入内存的数据线进行改动，增加这两个来源并添加选通器。 此外INT2在写入立即数的同时还需要SP-2， 也有一个立即数， 为简单起见， 就没有传递此立即数， 而是增加了一个ALU控制指令SUB2表示将输入1减2之后输出。\\

完成以上三条新指令的设计以及数据通路、ALU等部分的改造后， 我立即进行了测试， 直接写这三条指令， 经过几次试验改掉了一些小错误之后就可以运行了。 \\

之后的任务只要在相应中断发生时依次放出这三条指令即可。 为了实现这一功能， 我在IF段增加了一个用于中断处理的模块，当其检测到当前取出的指令为INT之后， 则将PC寄存器锁住， 然后依次发出上述四条语句， 结束后再解锁PC寄存器(实际上是在放出第四条NOP时即解锁)， 使其能够顺利跳转， 完成中断。 中断处理模块内部使用一个状态机来记录当前运行到了哪一步。 时钟中断和外部中断类似， 都是在外部给出一个1位信号， 信号为1时表示发生中断， 此信号传给中断处理模块， 变更状态机状态， 从而实现中断。 但是时钟中断以及外部中断并未对应一条指令， 所以注意要压栈的PC值需要减一， 这一步也是在IF段做的， 将PC值也传入中断处理模块， 再将处理后的结果送至IF-ID段间寄存器。

\section{指令缓存}
如前文所述，由于在我们的实验中访存的时间开销与CPU频率相同，我们设计的指令缓存的作用是与现在的计算机完全不一样的。我们数据缓存的目的并不是一次从内存中读出一段内容来节省时间，而是通过缓存一部分指令，减少结构冲突发生的概率，这在处理程序的循环结构中非常有用。\\

具体设计时，根据地址的后5位分为32路，每路包含1位的ValidBit、11位的地址位和16位的数据位。每次读取指令时，首先根据地址的后5位判断是否有相同地址的内容，如果找到则可以避免一次结构冲突，否则仍然需要访问内存读取数据，并更新缓存中的内容。\\
\section{分时多进程}
我们实现了一个简单的分时多进程的系统， 利用时钟中断在两个进程之间进行切换。\\

原本计划需要改动kernel程序， 来实现此功能， 但在阅读Kernel程序之后， 发现其中并未将IH值写死， 可以通过改变IH值来设置新的一个中断处理程序。 另外由于此部分拓展会涉及较多汇编代码， 在Terminal中一条条写指令显然不现实(而且Terminal中还不支持label标号)， 于是对Terminal进行了一点改动， 增加了装载二进制程序到某一内存位置。 另外消除了原先只能操作拓展RAM的限制， 可以操作更多的内存也使得之后的程序编写更为简单。\\

我们所设计的简单分时系统其实就是给定两个程序(开始地址分别为0x4000和0x5000)， 另有一个中断处理程序(装载在0x3000)， 每次发生中断时， 会调用中断处理程序， 而中断处理程序只要将寄存器值保存在特定位置(对第一个进程0x4700，对第二个进程0x5700)， 然后切换到另一个进程， 装入对应的另一进程的寄存器值， 并跳转回该进程原来的位置即可。 \\

实验过程中使用了两个简单的进程， 一个进程循环输出字符`A'， 另一个循环输出字符`B'。 源程序名分别为 `test.txt' 和 `test2.txt'。 实验中输出是输出至2号串口， 在另一台计算机上通过串口调试精灵显示的(详见下一部分).\\

此部分涉及的汇编代码在int目录下。 而改动后的Terminalinal代码位于Terminal目录下。\\

\section{双串口通信} 
支持简单的双串口通信， 实际实验过程中使用串口1在电脑上通过Terminal和箱子ROM上的Kernel对其进行控制。 然后运行进程输出至串口2在另一台电脑上进行观察。 \\ 

实现方式也很简单。 Kernel程序开始时已经将两个串口都初始化了， 只要将其中轮询代码抄过来改为对端口2的操作即可， 由于有改动后可装入二进制文件的Terminal程序帮助， 事实上只要写一个汇编程序， 然后使用汇编器进行汇编， 然后写入RAM中即可。  \\

